#include <gtest/gtest.h>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <iostream>

// FIFO queue
template<typename T>
class queue {
    std::deque<T> q;
    const size_t max_size;
    mutable std::mutex m;
    std::condition_variable c;
public:
    explicit queue(const size_t &max_size = 0) : max_size(max_size) {}

    bool empty() const {
        std::lock_guard<std::mutex> lock(m);
        return q.empty();
    }

    size_t size() const {
        std::lock_guard<std::mutex> lock(m);
        return q.size();
    }

    T &front() {
        std::lock_guard<std::mutex> lock(m);
        return q.front();
    }

    T const &front() const {
        std::lock_guard<std::mutex> lock(m);
        return q.front();
    }

    void put(T const &data) {
        std::lock_guard<std::mutex> lock(m);
        if ((max_size != 0) and (q.size() == max_size)) q.pop_front();
        q.push_back(data);
        c.notify_one();
    }

    void put(T &&data) {
        std::lock_guard<std::mutex> lock(m);
        if ((max_size != 0) and (q.size() == max_size)) q.pop_front();
        q.push_back(std::move(data));
        c.notify_one();
    }

    T get() {
        std::unique_lock<std::mutex> lock(m);
        while (q.empty()) c.wait(lock);
        T val = q.front();
        q.pop_front();
        return val;
    }

    std::vector<T> to_vector() const {
        std::lock_guard<std::mutex> lock(m);
        std::vector<T> ret;
        for (const auto &e:q)
            ret.push_back(e);
        return ret;
    }
};

class QueueTest : public ::testing::Test {
protected:
    static void SetUpTestSuite() {
        std::cout << "QueueTest::SetUpTestSuite" << std::endl;
    }

    static void TearDownTestSuite() {
        std::cout << "QueueTest::TearDownTestSuite" << std::endl;
    }

    void SetUp() override {
        q1_.put(1);
        q2_.put(2);
        q2_.put(3);
    }

    void TearDown() override {}

    queue<int> q0_;
    queue<int> q1_;
    queue<int> q2_;
};

TEST_F(QueueTest, check_queue_size) {
    EXPECT_EQ(q0_.size(), 0);
    EXPECT_TRUE(q0_.empty());
    EXPECT_EQ(q1_.size(), 1);
    EXPECT_FALSE(q1_.empty());
    EXPECT_EQ(q2_.size(), 2);
}

TEST_F(QueueTest, check_queue_work) {
    EXPECT_EQ(q1_.get(), 1);
    EXPECT_EQ(q1_.size(), 0);
    EXPECT_EQ(q2_.get(), 2);
    EXPECT_EQ(q2_.size(), 1);
    EXPECT_EQ(q2_.front(), 3);
    EXPECT_EQ(q2_.get(), 3);
    EXPECT_EQ(q2_.size(), 0);
}